/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package analisadorSemantico;

import analisadorSintatico.No;
import analisadorSintatico.NoInterno;
import analisadorSintatico.Nofolha;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import modelo.ErroSemantico;
import modelo.Funcao;
import modelo.IErro;
import modelo.IndentificadorJaDeclarado;
import modelo.Parametro;
import modelo.ParametroIncompativel;
import modelo.Procedimento;
import modelo.Simbolo;
import modelo.TipoIdentificador;
import modelo.Token;
import modelo.Variavel;
import modelo.tipos.TipoToken;
import modelo.tipos.TipoVariavel;

/**
 *
 * @author DEP
 */
public class AnalisadorSemantico {

    private List<IErro> errosSemanticos;
    private List<Token> tokens;
    private Escopo escopoAtual = null;
    private Set<Token> conjuntoDeDeclaracoes;
    private Set<Token> conjuntoDeParametros;
    private VerificacaoDeTipos verificacaoAtribuicao;
    private VerificacaoDeTipos verificacaoExpressao;
    private Procedimento procedimento;
    private Funcao funcao;
    private boolean leituraDeclaracoes = false;
    private boolean leituraParametro = false;
    private boolean leituraExpressao = false;
    private List<Simbolo> simbolos;
    private NoInterno raiz;

    public AnalisadorSemantico(List<Token> tokens, NoInterno raiz) {
        this.tokens = tokens;
        this.raiz = raiz;
        simbolos = new ArrayList<>();
    }

    public List<Simbolo> getTabelaSimbolos() {
        return simbolos;
    }

    public String getErrosSemanticos() {
        String erroString = "";
        for (IErro erro : errosSemanticos) {
            ErroSemantico erroSemantico = (ErroSemantico) erro;
            erroString += erroSemantico.errosToString();
        }
        return erroString;
    }

    private void criarEscopo(String nome) {
        escopoAtual = new Escopo(escopoAtual, nome);
    }

    private void addNaTabela() {
        Set<String> chaves = escopoAtual.getSimbolos().keySet();
        for (String chave : chaves) {
            if (chave != null) {
                simbolos.add(new Simbolo(chave, escopoAtual.getSimbolos().get(chave)));
            }
        }
    }

    private void destruirEscopo() {
        addNaTabela();
        escopoAtual = escopoAtual.getPai();
    }

    public void analisar() {
        errosSemanticos = new ArrayList<>();
        escopoAtual = null;
        try {
            programa();
            encontraTokenRepitaPare(raiz);
        } catch (ErroSemantico es) {
            errosSemanticos.add(es);
        }
    }

    private void programa() throws IndentificadorJaDeclarado, ErroSemantico {

        criarEscopo("Global");
        consumir(TipoToken.PALAVRA_RESERVADA, "programa");

        escopoAtual.addPrograma(consumir(TipoToken.IDENTIFICADOR).getToken(), lerTokenDaLista().getLinha());
        consumir(TipoToken.PONTOEV);
        declaracoes();
        bloco();
        consumir(TipoToken.PONTO);
        destruirEscopo();

    }

    private void declaracoes() throws ErroSemantico {
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "var")) {
            declaracaoVariavel();
        }
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "constante")) {
            declaracaoConstante();
        }
        declProcedimento();
    }

    private void declProcedimento() throws ErroSemantico {
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "procedimento")
                || proximoToken(TipoToken.PALAVRA_RESERVADA, "funcao")) {

            declProc();
            declProcedimento();

        }

    }

    private void declProc() throws ErroSemantico {
        leituraDeclaracoes = true;

        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "procedimento")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            procedimento = new Procedimento();
            Token t = consumir(TipoToken.IDENTIFICADOR);
            procedimento.setNome(t.getToken());
            procedimento.setLinha(t.getLinha());
            criarEscopo("Procedimento " + t.getToken());
            if (proximoToken(TipoToken.PARENTESES_ABRE)) {
                consumir(TipoToken.PARENTESES_ABRE);
                leituraParametro = true;
                parametros();
                consumir(TipoToken.PARENTESES_FECHA);
                leituraParametro = false;
            }
            consumir(TipoToken.PONTOEV);
            if (proximoToken(TipoToken.PALAVRA_RESERVADA, "var")) {
                declaracaoVariavel();
            }

            bloco();
            destruirEscopo();
            escopoAtual.addProcedimento(procedimento);
            consumir(TipoToken.PONTOEV);

        } else {
            consumir(TipoToken.PALAVRA_RESERVADA, "funcao");
            procedimento = new Procedimento();
            Token t = consumir(TipoToken.IDENTIFICADOR);
            procedimento.setNome(t.getToken());
            procedimento.setLinha(t.getLinha());

            criarEscopo("Função " + t.getToken());
            if (proximoToken(TipoToken.PARENTESES_ABRE)) {
                consumir(TipoToken.PARENTESES_ABRE);
                leituraParametro = true;
                parametros();
                consumir(TipoToken.PARENTESES_FECHA);
                leituraParametro = false;
            }
            consumir(TipoToken.DOIS_PONTOS);
            funcao = new Funcao(procedimento);
            funcao.setRetorno(tipo());
            escopoAtual.getPai().addfuncao(funcao);
            consumir(TipoToken.PONTOEV);
            if (proximoToken(TipoToken.PALAVRA_RESERVADA, "var")) {
                declaracaoVariavel();
            }
            bloco();
            destruirEscopo();
            //           escopoAtual.addfuncao(funcao);
            consumir(TipoToken.PONTOEV);

        }


    }

    private void parametros() throws ErroSemantico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {

            declVarListProc();

        }
    }

    private void bloco() throws ErroSemantico {
        leituraDeclaracoes = false;

        consumir(TipoToken.PALAVRA_RESERVADA, "inicio");
        instrucoes();
        consumir(TipoToken.PALAVRA_RESERVADA, "fim");

    }

    private void instrucoes() throws ErroSemantico {
        if (isInstrucao()) {

            inst();
            instrucoes();

        }
    }

    private boolean isInstrucao() {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "se")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "enquanto")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "repita")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "pare")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "continua")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "inicio")) {
            return true;
        } else {
            return false;
        }
    }
    private VerificacaoDeParametros verificacaoParametro;
    private boolean leituraPassagemParametro = false;

    private void inst() throws ErroSemantico {

        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            Token k = consumir(TipoToken.IDENTIFICADOR);
            TipoIdentificador i = null;
            try {
                i = escopoAtual.getIndentificador(k.getToken(), k.getLinha());
            } catch (ErroSemantico es) {
                errosSemanticos.add(es);
            }
            if (proximoToken(TipoToken.ATRIBUICAO)) {
                if (i != null) {
                    verificacaoAtribuicao = new VerificacaoDeTipos(k.getLinha());
                    if (i instanceof Variavel) {
                        verificacaoAtribuicao.push(((Variavel) i).getTipo());
                    } else if (i instanceof Funcao) {
                        verificacaoAtribuicao.push(((Funcao) i).getTipo());
                    }
                    verificacaoAtribuicao.push(expressao());
                    try {
                        verificacaoAtribuicao.avaliarAtribuicao();
                    } catch (ErroSemantico es) {
                        errosSemanticos.add(es);
                    }
                } else {
                    expressao();
                }
            } else if (proximoToken(TipoToken.PARENTESES_ABRE)) {
                // if (i != null) {
                verificacaoParametro = new VerificacaoDeParametros(k.getLinha());
                verificacaoParametro.setProcedimento((Procedimento) i);
                leituraPassagemParametro = true;
                expressao();
                try {
                    leituraPassagemParametro = false;
                    verificacaoParametro.avaliar();
                } catch (ErroSemantico es) {
                    errosSemanticos.add(es);
                }
                // }//else{
                //   expressao();
                //  }
            } else if (proximoToken(TipoToken.COLCHETES_ABRE)) {
                expressao();
            }



            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "se")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            expr();
            consumir(TipoToken.PALAVRA_RESERVADA, "entao");
            corpoEstrut();
            stmIf();
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "enquanto")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            expr();
            consumir(TipoToken.PALAVRA_RESERVADA, "faca");
            corpoEstrut();
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "repita")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            corpoEstrut();
            consumir(TipoToken.PALAVRA_RESERVADA, "ate");
            expr();
            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "pare")) {

            consumir(TipoToken.PALAVRA_RESERVADA);
            consumir(TipoToken.PONTOEV);

        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "continua")) {
//           
            consumir(TipoToken.PALAVRA_RESERVADA);
            consumir(TipoToken.PONTOEV);

        } else {
            bloco();
            consumir(TipoToken.PONTOEV);
        }

    }

    private void encontraTokenRepitaPare(NoInterno raiz) {
        NoInterno noAtual = raiz;
        if (noAtual != null) {
            for (No no : noAtual.getFilhos()) {
                if (no instanceof Nofolha) {
                    Nofolha folha = (Nofolha) no;
                    if (folha.getToken().getToken().compareToIgnoreCase("continua") == 0) {
                        if (!verificaLaco(noAtual)) {
                            errosSemanticos.add(new ParametroIncompativel("\"CONTINUA\" utilizado fora de \"ENQUANTO\" ou \"REPITA\" na linha " + folha.getToken().getLinha() + ".\n"));
                        }
                    }

                    if (folha.getToken().getToken().compareToIgnoreCase("pare") == 0) {
                        if (!verificaLaco(noAtual)) {
                            errosSemanticos.add(new ParametroIncompativel("\"PARE\" utilizado fora de \"ENQUANTO\" ou \"REPITA\" na linha " + folha.getToken().getLinha() + ".\n"));
                        }
                    }
                } else {
                    if (no instanceof NoInterno) {
                        NoInterno noi = (NoInterno) no;
                        encontraTokenRepitaPare(noi);
                    }

                }
            }
        }


    }

    private boolean verificaLaco(NoInterno noAtual) {

        boolean encontrou = false;
        if (noAtual != null) {
            for (No no : noAtual.getFilhos()) {
                if (no instanceof Nofolha) {
                    Nofolha folha = (Nofolha) no;
                    if (folha.getToken().getToken().compareToIgnoreCase("enquanto") == 0
                            || folha.getToken().getToken().compareToIgnoreCase("repita") == 0) {
                        encontrou = true;
                    }
                }
            }
            if (!encontrou) {
                encontrou = verificaLaco(noAtual.getPai());
            }
        }
        return encontrou;
    }

    private void expressoes() throws ErroSemantico {
        if (proximoToken(TipoToken.SEPARADOR)) {

            consumir(TipoToken.SEPARADOR);
            expressoes2();

        } else if (!proximoToken(TipoToken.PARENTESES_FECHA)) {
            leituraExpressao = true;
            verificacaoExpressao = new VerificacaoDeTipos(lerTokenDaLista().getLinha());

            expr();
            leituraExpressao = false;
            try {
                verificacaoParametro.push(verificacaoExpressao.avaliar());
            } catch (ErroSemantico es) {
                errosSemanticos.add(es);
            }
            expressoes();

        }
    }

    private void expressoes2() throws ErroSemantico {
        leituraExpressao = true;
        verificacaoExpressao = new VerificacaoDeTipos(lerTokenDaLista().getLinha());
        expr();
        leituraExpressao = false;
        try {
            verificacaoParametro.push(verificacaoExpressao.avaliar());
        } catch (ErroSemantico es) {
            errosSemanticos.add(es);
        }
        expressoes();

    }

    private TipoVariavel expressao() throws ErroSemantico {

        TipoVariavel tvar = null;
        if (proximoToken(TipoToken.ATRIBUICAO)) {
            Token t = consumir(TipoToken.ATRIBUICAO);
            leituraExpressao = true;
            verificacaoExpressao = new VerificacaoDeTipos(t.getLinha());

            expr();
            leituraExpressao = false;
            try {
                tvar = verificacaoExpressao.avaliar();
            } catch (ErroSemantico es) {
                errosSemanticos.add(es);
            }

        } else if (proximoToken(TipoToken.COLCHETES_ABRE)) {
            consumir(TipoToken.COLCHETES_ABRE);
            expr();
            consumir(TipoToken.COLCHETES_FECHA);
            consumir(TipoToken.ATRIBUICAO);
            expr();
        } else if (proximoToken(TipoToken.PARENTESES_ABRE)) {
            consumir(TipoToken.PARENTESES_ABRE);

            expressoes();
            consumir(TipoToken.PARENTESES_FECHA);
        }

        return tvar;
    }

    private void corpoEstrut() throws ErroSemantico {

        inst();

    }

    private void stmIf() throws ErroSemantico {
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "senao")) {

            consumir(TipoToken.PALAVRA_RESERVADA);
            corpoEstrut();

        }
    }

    private void declaracaoVariavel() throws ErroSemantico {
        leituraDeclaracoes = true;

        consumir(TipoToken.PALAVRA_RESERVADA, "var");
        declVarList();

    }

    private void declVarListProc() throws ErroSemantico {

        declVarProc();
        declVarListProc2();

    }

    private void declVarList() throws ErroSemantico {

        declVar();
        declVarList2();

    }

    private void declVarList2() throws ErroSemantico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {

            declVar();
            declVarList2();

        }
    }

    private void declVarListProc2() throws ErroSemantico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {

            declVarProc();
            declVarListProc2();

        }
    }

    private void declVar() throws IndentificadorJaDeclarado, ErroSemantico {
        conjuntoDeDeclaracoes = new LinkedHashSet<>();
        conjuntoDeParametros = new LinkedHashSet<>();

        variavel();
        conjuntoIds();
        consumir(TipoToken.DOIS_PONTOS);

        List<ErroSemantico> lista = escopoAtual.addVariaveis(conjuntoDeDeclaracoes, tipo(), linhaTipo);
        if (lista != null) {
            errosSemanticos.addAll(lista);
        }

        consumir(TipoToken.PONTOEV);


    }

    private void declVarProc() throws ErroSemantico {
        conjuntoDeDeclaracoes = new HashSet<>();
        conjuntoDeParametros = new LinkedHashSet<>();

        variavel();
        conjuntoIds();
        consumir(TipoToken.DOIS_PONTOS);
        TipoVariavel tipovar = tipo();
        List<ErroSemantico> lista = escopoAtual.addVariaveis(conjuntoDeDeclaracoes, tipovar, linhaTipo);
        if (lista != null) {
            errosSemanticos.addAll(lista);
        }
        lista = procedimento.addParametros(conjuntoDeParametros, tipovar);
        if (lista != null) {
            errosSemanticos.addAll(lista);
        }
        if (proximoToken(TipoToken.PONTOEV)) {
            consumir(TipoToken.PONTOEV);
            declVarProc();
        }


    }

    private void conjuntoIds() throws ErroSemantico {
        if (proximoToken(TipoToken.SEPARADOR)) {

            consumir(TipoToken.SEPARADOR);
            variavel();
            conjuntoIds();

        }
    }

    private void declaracaoConstante() throws ErroSemantico {
        leituraDeclaracoes = true;

        consumir(TipoToken.PALAVRA_RESERVADA, "constante");
        declConsList();

    }

    private void declConsList() throws ErroSemantico {

        declCons();
        declConsList2();

    }

    private void declConsList2() throws ErroSemantico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {

            declCons();
            declConsList2();

        }
    }

    private void declCons() throws ErroSemantico {

        Token var = consumir(TipoToken.IDENTIFICADOR);

        if (proximoToken(TipoToken.DOIS_PONTOS)) {
            consumir(TipoToken.DOIS_PONTOS);
            try {
                escopoAtual.addConstante(var.getToken(), tipo(), var.getLinha()); //tipo()
            } catch (IndentificadorJaDeclarado vjd) {
                errosSemanticos.add(vjd);
            }
            consumir(TipoToken.COMPARADOR, "=");
            valor();
            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.COMPARADOR, "=")) {
            consumir(TipoToken.COMPARADOR, "=");

            TipoVariavel tipovar = valor();
            try {
                if (tipovar != null) {
                    escopoAtual.addConstante(var.getToken(), tipovar, var.getLinha());
                } else {
                    escopoAtual.addConstante(var.getToken(), TipoVariavel.INTEIRO, var.getLinha()); //sem tipo
                }
            } catch (IndentificadorJaDeclarado vjd) {
                errosSemanticos.add(vjd);
            }

            consumir(TipoToken.PONTOEV);
        } else {
        }


    }
    private int linhaTipo;

    private TipoVariavel tipo() {
        TipoVariavel tipovar = null;

        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "inteiro")) {
            linhaTipo = consumir(TipoToken.PALAVRA_RESERVADA).getLinha();
            tipovar = TipoVariavel.INTEIRO;

        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "real")) {
            linhaTipo = consumir(TipoToken.PALAVRA_RESERVADA).getLinha();
            tipovar = TipoVariavel.REAL;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "char")) {
            linhaTipo = consumir(TipoToken.PALAVRA_RESERVADA).getLinha();
            tipovar = TipoVariavel.CHAR;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "string")) {
            linhaTipo = consumir(TipoToken.PALAVRA_RESERVADA).getLinha();
            tipovar = TipoVariavel.STRING;
        } else {
        }

        return tipovar;
    }

    private TipoVariavel valor() throws ErroSemantico {

        TipoVariavel tipovar;
        if (proximoToken(TipoToken.LITERAL)) {
            consumir(TipoToken.LITERAL);
            tipovar = TipoVariavel.STRING;
        } else {
            tipovar = unario();
        }

        return tipovar;
    }

    private TipoVariavel unario() throws ErroSemantico {

        TipoVariavel tipovar;
        if (proximoToken(TipoToken.OPERADOR, "+")) {
            consumir(TipoToken.OPERADOR);
            tipovar = fator();
        } else if (proximoToken(TipoToken.OPERADOR, "-")) {
            consumir(TipoToken.OPERADOR);
            tipovar = fator();
        } else {
            tipovar = fator();
        }

        return tipovar;
    }

    private TipoVariavel fator() throws ErroSemantico {

        TipoVariavel tipovar = null;
        if (proximoToken(TipoToken.NUMERO_INTEIRO)) {
            consumir(TipoToken.NUMERO_INTEIRO);
            tipovar = TipoVariavel.INTEIRO;
            if (leituraExpressao) {
                verificacaoExpressao.push(tipovar);
            }

        } else if (proximoToken(TipoToken.NUMERO_REAL)) {
            consumir(TipoToken.NUMERO_REAL);
            tipovar = TipoVariavel.REAL;
            if (leituraExpressao) {
                verificacaoExpressao.push(tipovar);
            }

        } else if (proximoToken(TipoToken.PARENTESES_ABRE)) { 
            consumir(TipoToken.PARENTESES_ABRE);
            leituraExpressao = true;
      
            expr();
            leituraExpressao = false;
       
            consumir(TipoToken.PARENTESES_FECHA);
        } else if (proximoToken(TipoToken.LITERAL)) {
            consumir(TipoToken.LITERAL);
            tipovar = TipoVariavel.STRING;
            if (leituraExpressao) {
                verificacaoExpressao.push(tipovar);
            }

        } else if (proximoToken(TipoToken.CHAR)) {
            consumir(TipoToken.CHAR);
            tipovar = TipoVariavel.CHAR;
            if (leituraExpressao) {
                verificacaoExpressao.push(tipovar);
            }

        } else {
            variavel();
        }


        return tipovar;
    }
    

    private void expr() throws ErroSemantico {

        exprComparacao();
        expr2();

    }

    private void expr2() throws ErroSemantico {
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "ou")) {

            consumir(TipoToken.PALAVRA_RESERVADA);
            exprComparacao();
            expr2();

        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "e")) {

            consumir(TipoToken.PALAVRA_RESERVADA);
            exprComparacao();
            expr2();

        }
    }

    private void exprComparacao() throws ErroSemantico {

        exprOp();
        exprComparacao2();

    }

    private void exprComparacao2() throws ErroSemantico {
        if (proximoToken(TipoToken.COMPARADOR)) {

            consumir(TipoToken.COMPARADOR);
            exprOp();
            exprComparacao2();

        }

    }

    private void variavel() throws ErroSemantico {

        Token var = consumir(TipoToken.IDENTIFICADOR);

        if (leituraDeclaracoes) {
            //variavel
            if (verificaAdd(conjuntoDeDeclaracoes, var)) {
                conjuntoDeDeclaracoes.add(var);
            } else {
                errosSemanticos.add(new IndentificadorJaDeclarado(var.getToken(), var.getLinha(), new Variavel()));
            }

            if (leituraParametro) {
                if (verificaAdd(conjuntoDeParametros, var)) {
                    conjuntoDeParametros.add(var);
                } else {
                    errosSemanticos.add(new IndentificadorJaDeclarado(var.getToken(), var.getLinha(), new Parametro()));
                }
            }
        }
        if (leituraExpressao) {
            try {
                TipoIdentificador v = escopoAtual.getIndentificador(var.getToken(), var.getLinha());
                verificacaoExpressao.push(v.getTipo());
            } catch (ErroSemantico es) {
                errosSemanticos.add(es);
            }
        }
//        if (leituraPassagemParametro) {
//            try {
//                TipoIdentificador v = escopoAtual.getIndentificador(var.getToken(), var.getLinha());
//                verificacaoParametro.push(v.getTipo());
//            } catch (ErroSemantico es) {
//                errosSemanticos.add(es);
//            }
//        }

        variavel2();

    }

    private boolean verificaAdd(Set<Token> lista, Token var) {
        if (lista != null) {
            for (Token token : lista) {
                if (token.getToken().compareToIgnoreCase(var.getToken()) == 0) {
                    return false;
                }
            }
        }
        return true;
    }

    private void variavel2() throws ErroSemantico {
        if (proximoToken(TipoToken.COLCHETES_ABRE)) {
            TipoVariavel tvar;
            consumir(TipoToken.COLCHETES_ABRE);

            exprOp();
            consumir(TipoToken.COLCHETES_FECHA);

        }
    }

    private void exprOp() throws ErroSemantico {
        termo();
        exprOp2();
    }

    private void exprOp2() throws ErroSemantico {
        if (proximoToken(TipoToken.OPERADOR, "+")) {
            consumir(TipoToken.OPERADOR);
            termo();
            exprOp2();

        } else if (proximoToken(TipoToken.OPERADOR, "-")) {
            consumir(TipoToken.OPERADOR);
            termo();
            exprOp2();

        }
    }

    private void termo() throws ErroSemantico {

        unario();
        termo2();

    }

    private void termo2() throws ErroSemantico {
        if (proximoToken(TipoToken.OPERADOR, "*")) {

            consumir(TipoToken.OPERADOR);
            unario();
            termo2();

        } else if (proximoToken(TipoToken.OPERADOR, "/")) {

            consumir(TipoToken.OPERADOR);
            unario();
            termo2();

        }
    }

    private Token consumir(TipoToken token) {
        return consumir(token, null);
    }

    private Token consumir(TipoToken tipoToken, String nomeToken) {
        return consumirTokenDaLista();
    }

    private boolean proximoToken(TipoToken tipoToken) {
        return proximoToken(tipoToken, null);
    }

    private boolean proximoToken(TipoToken tipoToken, String tokenName) {
        Token atual = lerTokenDaLista();
        if (atual == null) {
            return false;
        }
        if (atual.getTipo() == tipoToken && tokenName == null) {
            return true;
        } else {
            if (atual.getTipo() == tipoToken && atual.getToken().equalsIgnoreCase(tokenName)) {
                return true;
            } else {
                return false;
            }
        }
    }

    private Token consumirTokenDaLista() {
        Token atual;
        try {
            atual = tokens.get(0);
            tokens.remove(0);
        } catch (IndexOutOfBoundsException e) {
            return null;
        }
        return atual;
    }

    private Token lerTokenDaLista() {
        try {
            return tokens.get(0);
        } catch (IndexOutOfBoundsException ex) {
            return null;
        }
    }
}
